/** @class utils
 utils - collection of utilities
 
 $Id: utils.cpp 2 2012-04-04 15:07:32Z gerhardus1@gmail.com $
 Versioning: a.b.c a is a major release, b represents changes or new features, c represents bug fixes. 
 @version 1.0.0   06/10/2011    Gerhardus Muller     Script created

 @note

 @todo
 
 @bug

 Copyright Gerhardus Muller
 */

#include "utils/utils.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#define ERROR_SYSCALL -1

logger utils::log = logger( "utils", loggerDefs::MIDLEVEL );

/**
 Construction
 */
utils::utils( )
  : object( "utils" )
{
}	// utils

/**
 Destruction
 */
utils::~utils()
{
}	// ~utils

/**
 * convert time into a string
 * @param time value to convert
 * @return string version
 * **/
std::string utils::timeToString( time_t t )
{
  if( t == 0 ) return std::string( );
  char sTime[128];
  struct tm *tmp;
  tmp = localtime( &t );
  strftime( sTime, sizeof(sTime), "%F %T", tmp );
  return std::string( sTime );
} // timeToString

/**
 * priviledge management routines as taken from:
 * Setuid Demystiﬁed, Hao Chen, David Wagner, Drew Dean
 * **/
int utils::dropPriviledgeTemp( uid_t newUid )
{
#ifdef PLATFORM_MAC
  if( seteuid(newUid) < 0 )
    return ERROR_SYSCALL;
  if( geteuid() != newUid )
    return ERROR_SYSCALL;
#else
  if( setresuid( -1, newUid, geteuid() ) < 0 )
    return ERROR_SYSCALL;
  if( geteuid() != newUid )
    return ERROR_SYSCALL;
#endif
  return 0;
} // dropPriviledgeTemp
int utils::dropPriviledgePerm( uid_t newUid )
{
#ifdef PLATFORM_MAC
  if( setuid(newUid) < 0 )
    return ERROR_SYSCALL;
  if( getuid() != newUid )
    return ERROR_SYSCALL;
#else
  uid_t ruid, euid, suid;
  if( setresuid( newUid, newUid, newUid ) < 0 )
    return ERROR_SYSCALL;
  if( getresuid( &ruid, &euid, &suid ) < 0 )
    return ERROR_SYSCALL;
  if( ruid!=newUid || euid!=newUid || suid!=newUid )
    return ERROR_SYSCALL;
#endif
  return 0;
} // dropPriviledgePerm
int utils::dropPriviledgePerm( )
{
#ifdef PLATFORM_MAC
  uid_t newUid = geteuid(); // retrieves the effective user id of the program
  if( setuid(newUid) < 0 )
    return ERROR_SYSCALL;
  if( getuid() != newUid )
    return ERROR_SYSCALL;
#else
  uid_t ruid, euid, suid, newUid;
  if( getresuid( &ruid, &newUid, &suid ) < 0 )
    return ERROR_SYSCALL;
  if( setresuid( newUid, newUid, newUid ) < 0 )
    return ERROR_SYSCALL;
  if( getresuid( &ruid, &euid, &suid ) < 0 )
    return ERROR_SYSCALL;
  if( ruid!=newUid || euid!=newUid || suid!=newUid )
    return ERROR_SYSCALL;
#endif
  return 0;
} // dropPriviledgePerm
// restores priviledges to the real user id
// the original script used the suid rather than the ruid ..
// not what we wanted
int utils::restorePriviledge( )
{
#ifdef PLATFORM_MAC
  uid_t newUid = getuid();  // retrieves the real user id - specified at login time
  if( setuid(newUid) < 0 )
    return ERROR_SYSCALL;
  if( getuid() != newUid )
    return ERROR_SYSCALL;
#else
  uid_t ruid, euid, suid;
  if( getresuid( &ruid, &euid, &suid ) < 0 )
    return ERROR_SYSCALL;
  if( setresuid( -1, ruid, -1 ) < 0 )
    return ERROR_SYSCALL;
  if( geteuid() != ruid )
    return ERROR_SYSCALL;
#endif
  return 0;
} // restorePriviledge

/**
 * set file ownership
 * specify either fd or fname and at least one of owner and group
 * **/
bool utils::setFileOwnership( int fd, const char* owner, const char* group )
{
  if( (owner==NULL) && (group==NULL) ) return true;

  int userId = -1;
  int groupId = -1;
  if( !lookupUserId( owner, group, userId, groupId ) )
    return false;

  if( (userId!=-1) || (groupId!=-1) )
  {
    int retVal = -1;
    retVal = fchown( fd, userId, groupId );
    if( retVal == -1 )
    {
      log.warn( log.LOGALWAYS, "setFileOwnership: fchown fd:%d to user:'%s'(%d) group:'%s'(%d) failed - %s", fd, owner, userId, group, groupId, strerror(errno) );
      return false;
    }
    else
      log.info( log.LOGALWAYS, "setFileOwnership: fchown fd:%d to user:'%s'(%d) group:'%s'(%d) ", fd, owner, userId, group, groupId );
  } // if

  return true;
} // setFileOwnership
bool utils::setFileOwnership( const char* fname, const char* owner, const char* group )
{
  if( (owner==NULL) && (group==NULL) ) return true;

  int userId = -1;
  int groupId = -1;
  if( !lookupUserId( owner, group, userId, groupId ) )
    return false;

  if( (userId!=-1) || (groupId!=-1) )
  {
    int retVal = -1;
    retVal = chown( fname, userId, groupId );
    if( retVal == -1 )
    {
      log.warn( log.LOGALWAYS, "setFileOwnership: chown fn:'%s' to user:'%s'(%d) group:'%s'(%d) failed - %s", fname, owner, userId, group, groupId, strerror(errno) );
      return false;
    } // if
    else
      log.info( log.LOGALWAYS, "setFileOwnership: chown fn:'%s' to user:'%s'(%d) group:'%s'(%d)", fname, owner, userId, group, groupId );
  } // if

  return true;
} // setFileOwnership
bool utils::lookupUserId( const char* owner, const char* group, int& userId, int& groupId )
{
  if( (owner==NULL) && (group==NULL) ) return true;

  if( (owner!=NULL) && (strlen(owner)>0) )
  {
    struct passwd* pw = getpwnam( owner );
    if( pw == NULL )
    {
      log.warn( log.LOGALWAYS, "lookupUserId: could not find user:'%s'", owner );
      return false;
    }
    else
    {
      userId = pw->pw_uid;
    } // else
  } // if
  if( (group!=NULL) && (strlen(group)>0) )
  {
    struct group* gr = getgrnam( group );
    if( gr == NULL )
    {
      log.warn( log.LOGALWAYS, "lookupUserId: could not find group:'%s'", group );
      return false;
    }
    else
    {
      groupId = gr->gr_gid;
    } // else
  } // if

  return true;
} // setFileOwnership

/**
 * print the exit status of a child
 * @param status
 * **/
std::string utils::printExitStatus( int status )
{
  std::ostringstream oss;
  if( WIFEXITED( status ) )
    oss << "normal exit code " << WEXITSTATUS( status );
  else if( WIFSIGNALED( status ) )
    oss << "child killed by signal " << strsignal( WTERMSIG( status ) );
  else if( WIFSTOPPED( status ) )
    oss << "child stopped by signal " << strsignal( WSTOPSIG( status ) );
//  else if( WIFCONTINUED( status ) )   // only available in kernel 2.6.10 or later
//    oss << "child continued";
  return oss.str();
} // printExitStatus

/**
 * base64 encodes a string
 * encoding / decoding from 
 * http://base64.sourceforge.net/b64.c
 * @param strIn - the string to be encoded
 * @return the encoded string
 * **/
std::string utils::base64Encode( const std::string& strIn )
{
  int lengthIn = strIn.size( );
  int count = 0;
  int outLength = 0;
  int len = 0;
#if 1
  const char* inPointer = strIn.c_str( );
  char* outBuffer = new char[2*lengthIn];
  char* outPointer = outBuffer;
  
  while( count < lengthIn )
  {
    if( count+3 <= lengthIn )
      len = 3;
    else
      len = lengthIn - count;
    
    encodeBlock( inPointer, outPointer, len );
    inPointer += 3;
    count += 3;
    outPointer += 4;
    outLength += 4;
  }
  *outPointer = '\0';
  std::string strOut( outBuffer, outLength );
  delete outBuffer;
  return strOut;
#else
  std::string str = strIn;
  return str;
#endif
} // base64Encode

/*
** Translation Table as described in RFC1113
*/
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/**
 * encodeblock
 *
 * encode 3 8-bit binary bytes as 4 '6-bit' characters
 * **/
void utils::encodeBlock( const char in[3], char out[4], int len )
{
  out[0] = cb64[ in[0] >> 2 ];
  out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
  out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
  out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
} // encodeBlock

/**
 * base64 decodes a string
 * @param strIn - the string to be decoded
 * @return the decoded string
 * **/
//string Archive::base64Decode( const string& strIn )
//{
//} // base64Decode

/**
 * strips a trailing cr/lf
 * @param str
 * @param bAll - if true then search the entire message for CRLFs
 * **/
void utils::stripTrailingCRLF( std::string& str, bool bAll )
{
  if( str.empty() ) return;
  if( bAll )
  {
    std::string::size_type pos = 0;
    std::string tokens( "\r\n" );
    while( (pos=str.find_first_of(tokens,pos)) != std::string::npos )
    {
      if( str[pos] == '\r' )
        str.replace( pos, 1, "\\r" );
      if( str[pos] == '\n' )
        str.replace( pos, 1, "\\n" );
      pos++;
    } // while
  } // if bAll
  else
  {
    std::string replacementChars;
    for( int len=str.size()-1; len > 0; len-- )
    {
      if( (str[len]=='\r') )
      {
        str.resize( len );
        replacementChars.insert( 0, "\\r" );
      } // if
      else if( (str[len]=='\n') )
      {
        str.resize( len );
        replacementChars.insert( 0, "\\n" );
      } // if
      else
        break;
    } // for

    if( !replacementChars.empty() )
      str.append( replacementChars );
  } // if bAll
} // stripTrailingCRLF

